﻿using Microsoft.Extensions.Caching.Distributed;
using StackExchange.Redis;
using System.Diagnostics.CodeAnalysis;

namespace Niaofei.Redis
{
    internal class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheItem, TCacheKey>
        where TCacheItem : class
    {
        private readonly string _cacheName;
        private readonly IDistributedCache _cache;
        private readonly ConnectionMultiplexer _redis;
        private readonly RedisJsonSerializer _redisJsonSerializer;

        public DistributedCache(IDistributedCache cache, ConnectionMultiplexer redis)
        {
            _cache = cache;
            _redis = redis;
            _cacheName = typeof(TCacheItem).Name;
            _redisJsonSerializer = new RedisJsonSerializer();
        }

        public TCacheItem? Get(TCacheKey key)
        {
            var keyText = this.GetKey(key);

            var bytes = _cache.Get(keyText);

            if (bytes == null)
            {
                return null;
            }

            return ToCacheItem(bytes);
        }

        public async Task<TCacheItem?> GetAsync(TCacheKey key, CancellationToken token = default)
        {
            var keyText = this.GetKey(key);

            var bytes = await _cache.GetAsync(keyText, token);

            if (bytes == null)
            {
                return null;
            }

            return ToCacheItem(bytes);
        }

        public KeyValuePair<TCacheKey, TCacheItem>[] GetMany(IEnumerable<TCacheKey> keys)
        {
            var cachedValues = new List<KeyValuePair<TCacheKey, TCacheItem>>();

            foreach (var key in keys)
            {
                var cacheItem = Get(key);

                if (cacheItem != null)
                {
                    cachedValues.Add(new(key, cacheItem));
                }
            }

            return cachedValues.ToArray();
        }

        public async Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetManyAsync(IEnumerable<TCacheKey> keys, CancellationToken token = default)
        {
            var cachedValues = new List<KeyValuePair<TCacheKey, TCacheItem>>();

            foreach (var key in keys)
            {
                var cacheItem = await GetAsync(key, token);

                if (cacheItem != null)
                {
                    cachedValues.Add(new(key, cacheItem));
                }
            }

            return cachedValues.ToArray();
        }

        public TCacheItem GetOrAdd(TCacheKey key, Func<TCacheItem> factory, Func<DistributedCacheEntryOptions>? optionsFactory = null)
        {
            var value = Get(key);

            if (value != null)
            {
                return value;
            }

            value = factory();

            Set(key, value, optionsFactory?.Invoke());

            return value;
        }

        public async Task<TCacheItem> GetOrAddAsync(TCacheKey key, Func<Task<TCacheItem>> factory, Func<DistributedCacheEntryOptions>? optionsFactory = null, CancellationToken token = default)
        {
            var value = await GetAsync(key, token);

            if (value != null)
            {
                return value;
            }

            value = await factory();

            await SetAsync(key, value, optionsFactory?.Invoke(), token);

            return value;
        }

        public KeyValuePair<TCacheKey, TCacheItem>[] GetOrAddMany(IEnumerable<TCacheKey> keys, Func<IEnumerable<TCacheKey>, List<KeyValuePair<TCacheKey, TCacheItem>>> factory, Func<DistributedCacheEntryOptions>? optionsFactory = null)
        {
            var result = keys.Select(m => new KeyValuePair<TCacheKey, TCacheItem>(m, Get(m))).ToArray();

            if (result.All(m => m.Value != null))
            {
                return result;
            }

            var missKeys = new List<TCacheKey>();
            var missingValuesIndex = new List<int>();

            for (int i = 0; i < result.Length; i++)
            {
                if (result[i].Value == null)
                {
                    missKeys.Add(result[i].Key);
                    missingValuesIndex.Add(i);
                }
            }

            var missingValues = factory.Invoke(missKeys).ToArray();
            var valueQueue = new Queue<KeyValuePair<TCacheKey, TCacheItem>>(missingValues);

            SetMany(missingValues, optionsFactory?.Invoke());

            foreach (var index in missingValuesIndex)
            {
                result[index] = valueQueue.Dequeue();
            }

            return result;
        }

        public async Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetOrAddManyAsync(IEnumerable<TCacheKey> keys, Func<IEnumerable<TCacheKey>, Task<List<KeyValuePair<TCacheKey, TCacheItem>>>> factory, Func<DistributedCacheEntryOptions>? optionsFactory = null, CancellationToken token = default)
        {
            var result = keys.Select(m => new KeyValuePair<TCacheKey, TCacheItem>(m, Get(m))).ToArray();

            if (result.All(m => m.Value != null))
            {
                return result;
            }

            var missKeys = new List<TCacheKey>();
            var missingValuesIndex = new List<int>();

            for (int i = 0; i < result.Length; i++)
            {
                if (result[i].Value == null)
                {
                    missKeys.Add(result[i].Key);
                    missingValuesIndex.Add(i);
                }
            }

            var missingValues = (await factory.Invoke(missKeys)).ToArray();
            var valueQueue = new Queue<KeyValuePair<TCacheKey, TCacheItem>>(missingValues);

            await SetManyAsync(missingValues, optionsFactory?.Invoke(), token);

            foreach (var index in missingValuesIndex)
            {
                result[index] = valueQueue.Dequeue();
            }

            return result;
        }

        public void Refresh(TCacheKey key)
        {
            _cache.Refresh(GetKey(key));
        }

        public async Task RefreshAsync(TCacheKey key, CancellationToken token = default)
        {
            await _cache.RefreshAsync(GetKey(key), token);
        }

        public void RefreshMany(IEnumerable<TCacheKey> keys)
        {
            foreach (var key in keys)
            {
                Refresh(key);
            }
        }

        public async Task RefreshManyAsync(IEnumerable<TCacheKey> keys, CancellationToken token = default)
        {
            foreach (var key in keys)
            {
                await RefreshAsync(key, token);
            }
        }

        public void Remove(TCacheKey key)
        {
            _cache.Remove(GetKey(key));
        }

        public async Task RemoveAsync(TCacheKey key, CancellationToken token = default)
        {
            await _cache.RemoveAsync(GetKey(key), token);
        }

        public void RemoveMany(IEnumerable<TCacheKey> keys)
        {
            foreach (var key in keys)
            {
                Remove(key);
            }
        }

        public async Task RemoveManyAsync(IEnumerable<TCacheKey> keys, CancellationToken token = default)
        {

            foreach (var key in keys)
            {
                await RemoveAsync(key, token);
            }
        }

        public void Set(TCacheKey key, TCacheItem value, DistributedCacheEntryOptions? options = null)
        {
            var textKey = GetKey(key);

            var bytes = _redisJsonSerializer.Serialize(value);

            if (options != null)
            {
                _cache.Set(textKey, bytes, options);
            }
            else
            {
                _cache.Set(textKey, bytes);
            }
        }

        public async Task SetAsync(TCacheKey key, TCacheItem value, DistributedCacheEntryOptions? options = null, CancellationToken token = default)
        {
            var textKey = GetKey(key);

            var bytes = _redisJsonSerializer.Serialize(value);

            if (options != null)
            {
                await _cache.SetAsync(textKey, bytes, options, token);
            }
            else
            {
                await _cache.SetAsync(textKey, bytes, token);
            }
        }

        public void SetMany(IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> items, DistributedCacheEntryOptions? options = null)
        {
            foreach (var (key, value) in items)
            {
                Set(key, value, options);
            }
        }

        public async Task SetManyAsync(IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> items, DistributedCacheEntryOptions? options = null, CancellationToken token = default)
        {
            foreach (var (key, value) in items)
            {
                await SetAsync(key, value, options, token);
            }
        }

        /// <summary>
        /// key命名
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        private string GetKey(TCacheKey key)
        {
            return $"c:{_cacheName},k:{key}";
        }

        /// <summary>
        /// byte转Value
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        private TCacheItem ToCacheItem(byte[] bytes)
        {
            return _redisJsonSerializer.Deserialize<TCacheItem>(bytes);
        }
    }

    internal class DistributedCache<TCacheItem> : IDistributedCache<TCacheItem> where TCacheItem : class
    {
        public IDistributedCache<TCacheItem, string> InternalCache { get; protected set; }

        public DistributedCache(IDistributedCache<TCacheItem, string> internalCache)
        {
            InternalCache = internalCache;
        }

        public TCacheItem? Get(string key)
        {
            return InternalCache.Get(key);
        }

        public async Task<TCacheItem?> GetAsync(string key, CancellationToken token = default)
        {
            return await InternalCache.GetAsync(key, token);
        }

        public KeyValuePair<string, TCacheItem>[] GetMany(IEnumerable<string> keys)
        {
            return InternalCache.GetMany(keys);
        }

        public async Task<KeyValuePair<string, TCacheItem>[]> GetManyAsync(IEnumerable<string> keys, CancellationToken token = default)
        {
            return await InternalCache.GetManyAsync(keys, token);
        }

        public TCacheItem GetOrAdd(string key, Func<TCacheItem> factory, Func<DistributedCacheEntryOptions>? optionsFactory = null)
        {
            return InternalCache.GetOrAdd(key, factory, optionsFactory);
        }

        public async Task<TCacheItem> GetOrAddAsync(string key, Func<Task<TCacheItem>> factory, Func<DistributedCacheEntryOptions>? optionsFactory = null, CancellationToken token = default)
        {
            return await InternalCache.GetOrAddAsync(key, factory, optionsFactory, token);
        }

        public KeyValuePair<string, TCacheItem>[] GetOrAddMany(IEnumerable<string> keys, Func<IEnumerable<string>, List<KeyValuePair<string, TCacheItem>>> factory, Func<DistributedCacheEntryOptions>? optionsFactory = null)
        {
            return InternalCache.GetOrAddMany(keys, factory, optionsFactory);
        }

        public Task<KeyValuePair<string, TCacheItem>[]> GetOrAddManyAsync(IEnumerable<string> keys, Func<IEnumerable<string>, Task<List<KeyValuePair<string, TCacheItem>>>> factory, Func<DistributedCacheEntryOptions>? optionsFactory = null, CancellationToken token = default)
        {
            return InternalCache.GetOrAddManyAsync(keys, factory, optionsFactory, token);
        }

        public void Refresh(string key)
        {
            InternalCache.Refresh(key);
        }

        public async Task RefreshAsync(string key, CancellationToken token = default)
        {
            await InternalCache.RefreshAsync(key, token);
        }

        public void RefreshMany(IEnumerable<string> keys)
        {
            InternalCache.RefreshMany(keys);
        }

        public async Task RefreshManyAsync(IEnumerable<string> keys, CancellationToken token = default)
        {
            await InternalCache.RefreshManyAsync(keys, token);
        }

        public void Remove(string key)
        {
            InternalCache.Remove(key);
        }

        public async Task RemoveAsync(string key, CancellationToken token = default)
        {
            await InternalCache.RemoveAsync(key, token);
        }

        public void RemoveMany(IEnumerable<string> keys)
        {
            InternalCache.RemoveMany(keys);
        }

        public async Task RemoveManyAsync(IEnumerable<string> keys, CancellationToken token = default)
        {
            await InternalCache.RemoveManyAsync(keys, token);
        }

        public void Set(string key, TCacheItem value, DistributedCacheEntryOptions? options = null)
        {
            InternalCache.Set(key, value, options);
        }

        public async Task SetAsync(string key, TCacheItem value, DistributedCacheEntryOptions? options = null, CancellationToken token = default)
        {
            await InternalCache.SetAsync(key, value, options, token);
        }

        public void SetMany(IEnumerable<KeyValuePair<string, TCacheItem>> items, DistributedCacheEntryOptions? options = null)
        {
            InternalCache.SetMany(items, options);
        }

        public async Task SetManyAsync(IEnumerable<KeyValuePair<string, TCacheItem>> items, DistributedCacheEntryOptions? options = null, CancellationToken token = default)
        {
            await InternalCache.SetManyAsync(items, options, token);
        }
    }
}
